// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2009 Corey Tabaka
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#include "debug.h"

#include <bits.h>
#include <lib/cbuf.h>
#include <lib/cmdline.h>
#include <lib/debuglog.h>
#include <platform.h>
#include <reg.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include <zircon/time.h>
#include <zircon/types.h>

#include <arch/x86.h>
#include <arch/x86/apic.h>
#include <dev/interrupt.h>
#include <kernel/spinlock.h>
#include <kernel/thread.h>
#include <kernel/timer.h>
#include <lk/init.h>
#include <platform/console.h>
#include <platform/debug.h>
#include <platform/pc.h>
#include <platform/pc/bootloader.h>
#include <vm/physmap.h>

#include "platform_p.h"

static const int uart_baud_rate = 115200;
static int uart_io_port = 0x3f8;
static uint64_t uart_mem_addr = 0;
static uint32_t uart_irq = ISA_IRQ_SERIAL1;

DebugUartInfo debug_uart_info() {
  DebugUartInfo::Type type;
  switch (bootloader.uart.type) {
    case ZBI_UART_PC_PORT:
      type = DebugUartInfo::Type::Port;
      break;
    case ZBI_UART_PC_MMIO:
      type = DebugUartInfo::Type::Mmio;
      break;
    default:
      type = DebugUartInfo::Type::None;
      break;
  }
  return DebugUartInfo{
      .mem_addr = uart_mem_addr,
      .io_port = static_cast<uint32_t>(uart_io_port),
      .irq = uart_irq,
      .type = type,
  };
}

cbuf_t console_input_buf;
static bool output_enabled = false;
uint32_t uart_fifo_depth;

// tx driven irq
static bool uart_tx_irq_enabled = false;
static event_t uart_dputc_event =
    EVENT_INITIAL_VALUE(uart_dputc_event, true, EVENT_FLAG_AUTOUNSIGNAL);
static spin_lock_t uart_spinlock = SPIN_LOCK_INITIAL_VALUE;

static uint8_t uart_read(uint8_t reg) {
  if (uart_mem_addr) {
    return (uint8_t)readl(uart_mem_addr + 4 * reg);
  } else {
    return (uint8_t)inp((uint16_t)(uart_io_port + reg));
  }
}

static void uart_write(uint8_t reg, uint8_t val) {
  if (uart_mem_addr) {
    writel(val, uart_mem_addr + 4 * reg);
  } else {
    outp((uint16_t)(uart_io_port + reg), val);
  }
}

static interrupt_eoi uart_irq_handler(void* arg) {
  spin_lock(&uart_spinlock);

  // see why we have gotten an irq
  for (;;) {
    uint8_t iir = uart_read(2);
    if (BIT(iir, 0))
      break;  // no valid interrupt

    // 3 bit identification field
    uint ident = BITS(iir, 3, 0);
    switch (ident) {
      case 0b0100:
      case 0b1100: {
        // rx fifo is non empty, drain it
        unsigned char c = uart_read(0);
        cbuf_write_char(&console_input_buf, c);
        break;
      }
      case 0b0010:
        // transmitter is empty, signal any waiting senders
        event_signal(&uart_dputc_event, true);
        // disable the tx irq
        uart_write(1, (1 << 0));  // just rx interrupt enable
        break;
      case 0b0110:     // receiver line status
        uart_read(5);  // read the LSR
        break;
      default:
        spin_unlock(&uart_spinlock);
        panic("UART: unhandled ident %#x\n", ident);
    }
  }

  spin_unlock(&uart_spinlock);
  return IRQ_EOI_DEACTIVATE;
}

static void platform_drain_debug_uart_rx(void) {
  while (uart_read(5) & (1 << 0)) {
    unsigned char c = uart_read(0);
    cbuf_write_char(&console_input_buf, c);
  }
}

static constexpr TimerSlack kSlack{ZX_MSEC(1), TIMER_SLACK_CENTER};

// for devices where the uart rx interrupt doesn't seem to work
static void uart_rx_poll(timer_t* t, zx_time_t now, void* arg) {
  const Deadline deadline(zx_time_add_duration(now, ZX_MSEC(10)), kSlack);
  timer_set(t, deadline, uart_rx_poll, NULL);
  platform_drain_debug_uart_rx();
}

void platform_debug_start_uart_timer();

void platform_debug_start_uart_timer(void) {
  static timer_t uart_rx_poll_timer;
  static bool started = false;

  if (!started) {
    started = true;
    timer_init(&uart_rx_poll_timer);
    const Deadline deadline(zx_time_add_duration(current_time(), ZX_MSEC(10)), kSlack);
    timer_set(&uart_rx_poll_timer, deadline, uart_rx_poll, NULL);
  }
}

static void init_uart() {
  /* configure the uart */
  int divisor = 115200 / uart_baud_rate;

  /* get basic config done so that tx functions */
  uart_write(1, 0);                                   // mask all irqs
  uart_write(3, 0x80);                                // set up to load divisor latch
  uart_write(0, static_cast<uint8_t>(divisor));       // lsb
  uart_write(1, static_cast<uint8_t>(divisor >> 8));  // msb
  // enable FIFO, rx FIFO reset, tx FIFO reset, 16750 64 byte fifo enable,
  // Rx FIFO irq trigger level at 14-bytes, must be done while divisor
  // latch is enabled in order to write the 16750 64 byte fifo enable bit
  uart_write(2, 0xe7);
  uart_write(3, 3);  // 8N1

  // Drive flow control bits high since we don't actively manage them
  uart_write(4, 0x3);

  /* figure out the fifo depth */
  uint8_t fcr = uart_read(2);
  if (BITS_SHIFT(fcr, 7, 6) == 3 && BIT(fcr, 5)) {
    // this is a 16750
    uart_fifo_depth = 64;
  } else if (BITS_SHIFT(fcr, 7, 6) == 3) {
    // this is a 16550A
    uart_fifo_depth = 16;
  } else {
    uart_fifo_depth = 1;
  }
}

bool platform_serial_enabled() { return bootloader.uart.type != ZBI_UART_NONE; }

// This just initializes the default serial console to be disabled.
// It's in a function rather than just a compile-time assignment to the
// bootloader structure because our C++ compiler doesn't support non-trivial
// designated initializers.
void pc_init_debug_default_early() { bootloader.uart.type = ZBI_UART_NONE; }

static void handle_serial_cmdline() {
  const char* serial_mode = gCmdline.GetString("kernel.serial");
  if (serial_mode == NULL) {
    return;
  }
  if (!strcmp(serial_mode, "none")) {
    bootloader.uart.type = ZBI_UART_NONE;
    return;
  }
  if (!strcmp(serial_mode, "legacy")) {
    bootloader.uart.type = ZBI_UART_PC_PORT;
    bootloader.uart.base = 0x3f8;
    bootloader.uart.irq = ISA_IRQ_SERIAL1;
    return;
  }

  // type can be "ioport" or "mmio"
  constexpr size_t kMaxTypeLen = 6 + 1;
  char type_buf[kMaxTypeLen];
  // Addr can be up to 32 characters (numeric in any base strtoul will take),
  // and + 1 for \0
  constexpr size_t kMaxAddrLen = 32 + 1;
  char addr_buf[kMaxAddrLen];

  char* endptr;
  const char* addr_start;
  const char* irq_start;
  size_t addr_len, type_len;
  unsigned long irq_val;

  addr_start = strchr(serial_mode, ',');
  if (addr_start == nullptr) {
    goto bail;
  }
  addr_start++;
  irq_start = strchr(addr_start, ',');
  if (irq_start == nullptr) {
    goto bail;
  }
  irq_start++;

  // Parse out the type part
  type_len = addr_start - serial_mode - 1;
  if (type_len + 1 > kMaxTypeLen) {
    goto bail;
  }
  memcpy(type_buf, serial_mode, type_len);
  type_buf[type_len] = 0;
  if (!strcmp(type_buf, "ioport")) {
    bootloader.uart.type = ZBI_UART_PC_PORT;
  } else if (!strcmp(type_buf, "mmio")) {
    bootloader.uart.type = ZBI_UART_PC_MMIO;
  } else {
    goto bail;
  }

  // Parse out the address part
  addr_len = irq_start - addr_start - 1;
  if (addr_len + 1 > kMaxAddrLen) {
    goto bail;
  }
  memcpy(addr_buf, addr_start, addr_len);
  addr_buf[addr_len] = 0;
  bootloader.uart.base = strtoul(addr_buf, &endptr, 0);
  if (endptr != addr_buf + addr_len) {
    goto bail;
  }

  // Parse out the IRQ part
  irq_val = strtoul(irq_start, &endptr, 0);
  if (*endptr != '\0' || irq_val > UINT32_MAX) {
    goto bail;
  }
  // For now, we don't support non-ISA IRQs
  if (irq_val >= NUM_ISA_IRQS) {
    goto bail;
  }
  bootloader.uart.irq = static_cast<uint32_t>(irq_val);
  return;

bail:
  bootloader.uart.type = ZBI_UART_NONE;
  return;
}

void pc_init_debug_early() {
  handle_serial_cmdline();

  switch (bootloader.uart.type) {
    case ZBI_UART_PC_PORT:
      uart_io_port = static_cast<uint32_t>(bootloader.uart.base);
      uart_irq = bootloader.uart.irq;
      break;
    case ZBI_UART_PC_MMIO:
      uart_mem_addr = (uint64_t)paddr_to_physmap(bootloader.uart.base);
      uart_irq = bootloader.uart.irq;
      break;
    case ZBI_UART_NONE:  // fallthrough
    default:
      bootloader.uart.type = ZBI_UART_NONE;
      return;
  }

  init_uart();

  output_enabled = true;

  dprintf(INFO, "UART: FIFO depth %u\n", uart_fifo_depth);
}

void pc_init_debug(void) {
  bool tx_irq_driven = false;
  /* finish uart init to get rx going */
  cbuf_initialize(&console_input_buf, 1024);

  if (!platform_serial_enabled()) {
    // Need to bail after initializing the input_buf to prevent uninitialized
    // access to it.
    return;
  }

  if ((uart_irq == 0) || gCmdline.GetBool("kernel.debug_uart_poll", false) || dlog_bypass()) {
    printf("debug-uart: polling enabled\n");
    platform_debug_start_uart_timer();
  } else {
    uart_irq = apic_io_isa_to_global(static_cast<uint8_t>(uart_irq));
    zx_status_t status = register_int_handler(uart_irq, uart_irq_handler, NULL);
    DEBUG_ASSERT(status == ZX_OK);
    unmask_interrupt(uart_irq);

    uart_write(1, (1 << 0));  // enable receive data available interrupt

    // modem control register: Auxiliary Output 2 is another IRQ enable bit
    const uint8_t mcr = uart_read(4);
    uart_write(4, mcr | 0x8);
    printf("UART: started IRQ driven RX\n");
    tx_irq_driven = true;
  }
  if (tx_irq_driven) {
    // start up tx driven output
    printf("UART: started IRQ driven TX\n");
    uart_tx_irq_enabled = true;
  }
}

void pc_suspend_debug(void) { output_enabled = false; }

void pc_resume_debug(void) {
  if (platform_serial_enabled()) {
    init_uart();
    output_enabled = true;
  }
}

/*
 * This is called when the FIFO is detected to be empty. So we can write an
 * entire FIFO's worth of bytes. Much more efficient than writing 1 byte
 * at a time (and then checking for FIFO to drain).
 */
static char* debug_platform_tx_FIFO_bytes(const char* str, size_t* len, bool* copied_CR,
                                          size_t* wrote_bytes, bool map_NL) {
  size_t i, copy_bytes;
  char* s = (char*)str;

  copy_bytes = MIN(uart_fifo_depth, *len);
  for (i = 0; i < copy_bytes; i++) {
    if (*s == '\n' && map_NL && !*copied_CR) {
      uart_write(0, '\r');
      *copied_CR = true;
      if (++i == copy_bytes)
        break;
      uart_write(0, '\n');
    } else {
      uart_write(0, *s);
      *copied_CR = false;
    }
    s++;
    (*len)--;
  }
  if (wrote_bytes != NULL)
    *wrote_bytes = i;
  return s;
}

/*
 * dputs() Tx is either polling driven (if the caller is non-preemptible
 * or earlyboot or panic) or blocking (and irq driven).
 * TODO - buffered Tx support may be a win, (lopri but worth investigating)
 * When we do that dputs() can be completely asynchronous, and return when
 * the write has been (atomically) deposited into the buffer, except when
 * we run out of room in the Tx buffer (rare) - we'd either need to spin
 * (if non-blocking) or block waiting for space in the Tx buffer (adding
 * support to optionally block in cbuf_write_char() would be easiest way
 * to achieve that).
 *
 * block : Blocking vs Non-Blocking
 * map_NL : If true, map a '\n' to '\r'+'\n'
 */
static void platform_dputs(const char* str, size_t len, bool block, bool map_NL) {
  spin_lock_saved_state_t state;
  bool copied_CR = false;
  size_t wrote;

  // drop strings if we haven't initialized the uart yet
  if (unlikely(!output_enabled))
    return;
  if (!uart_tx_irq_enabled)
    block = false;
  spin_lock_irqsave(&uart_spinlock, state);
  while (len > 0) {
    // Is FIFO empty ?
    while (!(uart_read(5) & (1 << 5))) {
      if (block) {
        /*
         * We want to Tx more and FIFO is empty, re-enable
         * Tx interrupts before blocking.
         */
        uart_write(1, (1 << 0) | (1 << 1));  // rx and tx interrupt enable
        spin_unlock_irqrestore(&uart_spinlock, state);
        event_wait(&uart_dputc_event);
      } else {
        spin_unlock_irqrestore(&uart_spinlock, state);
        arch_spinloop_pause();
      }
      spin_lock_irqsave(&uart_spinlock, state);
    }
    // Fifo is completely empty now, we can shove an entire
    // fifo's worth of Tx...
    str = debug_platform_tx_FIFO_bytes(str, &len, &copied_CR, &wrote, map_NL);
    if (block && wrote > 0) {
      // If blocking/irq driven wakeps, enable rx/tx intrs
      uart_write(1, (1 << 0) | (1 << 1));  // rx and tx interrupt enable
    }
  }
  spin_unlock_irqrestore(&uart_spinlock, state);
}

void platform_dputs_thread(const char* str, size_t len) {
  if (platform_serial_enabled()) {
    platform_dputs(str, len, true, true);
  }
}

void platform_dputs_irq(const char* str, size_t len) {
  if (platform_serial_enabled()) {
    platform_dputs(str, len, false, true);
  }
}

// polling versions of debug uart read/write
static int debug_uart_getc_poll(char* c) {
  // if there is a character available, read it
  if (uart_read(5) & (1 << 0)) {
    *c = uart_read(0);
    return 0;
  }

  return -1;
}

static void debug_uart_putc_poll(char c) {
  // while the fifo is non empty, spin
  while (!(uart_read(5) & (1 << 6))) {
    arch_spinloop_pause();
  }
  uart_write(0, c);
}

int platform_dgetc(char* c, bool wait) {
  if (platform_serial_enabled()) {
    return static_cast<int>(cbuf_read_char(&console_input_buf, c, wait));
  }
  return ZX_ERR_NOT_SUPPORTED;
}

// panic time polling IO for the panic shell
void platform_pputc(char c) {
  if (platform_serial_enabled()) {
    if (c == '\n')
      debug_uart_putc_poll('\r');
    debug_uart_putc_poll(c);
  }
}

int platform_pgetc(char* c, bool wait) {
  if (platform_serial_enabled()) {
    return debug_uart_getc_poll(c);
  }
  return ZX_ERR_NOT_SUPPORTED;
}

/*
 * Called on start of a panic.
 * When we do Tx buffering, drain the Tx buffer here in polling mode.
 * Turn off Tx interrupts, so force Tx be polling from this point
 */
void platform_debug_panic_start(void) { uart_tx_irq_enabled = false; }
